package bll

import (
	"context"
	"fmt"
	"gitlab.local/backend/pkg/cron"
	"go-micro.dev/v4/util/log"
	"pay/event"
	"pay/model"
	"pay/model/entity"
	"pay/store"
	"pay/store/postgres"
	"pay/utils"
	"sync"
	"time"
)

type timeoutTimer struct {
	*cron.Cron
	locker               sync.Mutex
	id2EntryId           map[int64]cron.EntryID
	iFeePlanPaymentOrder store.IFeePlanPaymentOrder
}

func init() {

	Register(&timeoutTimer{
		locker:               sync.Mutex{},
		iFeePlanPaymentOrder: postgres.FeePlanPaymentOrder,
		id2EntryId:           make(map[int64]cron.EntryID),
		Cron:                 cron.New(cron.WithLocation(time.Local), cron.WithSeconds()),
	})
}

func (a *timeoutTimer) init() func() {

	a.Start()
	event.Subscribe(event.CreateOrder, a.handleCreateOrder)
	event.Subscribe(event.CancelOrder, a.handleCancelOrder)
	event.Subscribe(event.OrderFinished, a.handleOrderFinished)

	event.Publish(context.Background(), event.CreateOrder, &event.OrderEventData{
		ExpiredAt: time.Now().Unix() + 5,
	})
	return func() {}
}

func (a *timeoutTimer) handleCancelOrder(ctx context.Context, e event.Event, data interface{}) {

	a.locker.Lock()
	defer a.locker.Unlock()

	var (
		evtData = data.(*event.OrderEventData)
	)
	_ = a.delete(evtData)
}

func (a *timeoutTimer) handleOrderFinished(ctx context.Context, e event.Event, data interface{}) {

}

func (a *timeoutTimer) handleCreateOrder(ctx context.Context, e event.Event, data interface{}) {

	a.locker.Lock()
	defer a.locker.Unlock()

	var (
		err       error
		entryId   cron.EntryID
		evtData   = data.(*event.OrderEventData)
		startTime = time.Unix(evtData.ExpiredAt, 0)
		spec      = fmt.Sprintf("%v %v %v %v %v *", startTime.Second(), startTime.Minute(), startTime.Hour(), startTime.Day(), int(startTime.Month()))
	)

	if _, ok := a.id2EntryId[evtData.Id]; !ok {
		entryId, err = a.AddFunc(spec, a.timerCallBack, evtData)
		if err != nil {
			log.Errorf("创建订单%v超时定时器错误:%+v", evtData.Id, err)
			return
		}
		a.id2EntryId[evtData.Id] = entryId
	}

	return
}

func (a *timeoutTimer) delete(evtData *event.OrderEventData) error {

	a.locker.Lock()
	defer a.locker.Unlock()
	var (
		err  error
		data = evtData
	)

	if entryId, ok := a.id2EntryId[data.Id]; ok {
		a.Remove(entryId)
		delete(a.id2EntryId, data.Id)
	}

	return err
}

func (a *timeoutTimer) timerCallBack(args ...interface{}) {

	var (
		data       = args[1].(*event.OrderEventData)
		ctx, _     = context.WithTimeout(context.Background(), time.Minute*3)
		timeFormat = "2006-01-02 15:04:05"
	)

	defer func() {
		_ = a.delete(&event.OrderEventData{Id: data.Id})
	}()

	var (
		err               error
		respPaymentOrders []*entity.FeePlanPaymentOrder
		request           = &model.FeePlanPaymentOrderListRequest{
			Index:  1,
			Size:   -1,
			Status: utils.GetValuePointer(int(model.OrderCreated)),
		}
	)
	if data.Id > 0 {
		request.Id = utils.GetValuePointer(data.Id)
	}

	_, respPaymentOrders, err = a.iFeePlanPaymentOrder.List(ctx, request, model.QueryOptions{
		OnlyList: true,
	})
	if err != nil {
		log.Error(fmt.Sprintf("try to update fee plan status to %v err, due to the err:%v when getting order", model.OrderExpired.String(), err))
		return
	}

	if !(respPaymentOrders != nil && len(respPaymentOrders) > 0) {
		log.Errorf(fmt.Sprintf("try to update fee plan status to %v err, due to the order:%v that does not exist", model.OrderExpired.String(), data.Id))
		return
	}

	for _, v := range respPaymentOrders {

		respPaymentOrder := v
		log.Info(fmt.Sprintf("fee plan task:%v has expired:%v,set failure status", respPaymentOrder.Id, time.Unix(data.ExpiredAt, 0).Format(timeFormat)))

		inStatus := model.PayOrderStatus(respPaymentOrder.Status)
		if inStatus != model.OrderCreated {
			log.Errorf("try to update fee plan status to %v err, due to is already to be set %v", model.OrderExpired.String(), model.PayOrderStatus(respPaymentOrder.Status).String())
			return
		}

		err = FeePlanPaymentOrder.Update(ctx, &model.FeePlanPaymentOrderUpdateRequest{
			Id:     utils.GetValuePointer(data.Id),
			Status: utils.GetValuePointer(int(model.OrderExpired)),
		})
		if err != nil {
			log.Errorf("try to update fee plan status to %v err, due to the err:%v when setting order status ", model.OrderExpired.String(), err)
			return
		}
	}
}
